const fs = require("fs");
const path = require("path");

// 我们需要 遍历AST，将用到的依赖收集起来。什么意思呢？其实就是将用import语句引入的文件路径收集起来。我们将收集起来的路径放到deps里。
const traverse = require("@babel/traverse").default;

// 分析模块的主要任务是 将获取到的模块内容 解析成AST语法树，这个需要用到一个依赖包@babel/parser
const parser = require("@babel/parser");

// 把获得的ES6的AST转化成ES5
const babel = require("@babel/core");

const getModuleInfo = (file) => {
	const body = fs.readFileSync(file, "utf-8");

	const ast = parser.parse(body, {
		sourceType: "module", //表示我们要解析的是ES模块
	});
	const deps = {};

	/* 
    ImportDeclaration方法代表的是对type类型为ImportDeclaration的节点的处理。
	console.log(ast.program.body); // 打印的结果中存在type: 'ImportDeclaration'，这个函数就是这个类型的节点做处理操作
    */
	traverse(ast, {
		ImportDeclaration({ node }) {
			const dirname = path.dirname(file);

			// console.log(ast.program.body); type为ImportDeclaration的value ，也就是入口文件import的路径"./add", "./minus"
			const importPath = node.source.value;
			const abspath = "./" + path.join(dirname, importPath); // 拼接

			deps[importPath] = abspath; // 收集入口文件中， 所有import的文件对应的地址
		},
	});

	// console.log(deps); // { './add': './src\\add', './minus': './src\\minus' }

	const { code } = babel.transformFromAst(ast, null, {
		presets: ["@babel/preset-env"],
	});

	// 该模块的路径（file），该模块的依赖（deps），该模块转化成es5的代码
	const moduleInfo = { file, deps, code };
	return moduleInfo;
};

/**
 * 递归获取所有依赖
 * @param {*} file
 */
const parseModules = (file) => {
	const entry = getModuleInfo(file);
	const allModuleInfo = [entry]; // 所有模块信息
	const depsGraph = {};

	allModuleInfo.forEach((module) => {
		// { './add': './src\\add', './minus': './src\\minus' },
		const deps = module.deps;
		if (deps) {
			for (const key in deps) {
				allModuleInfo.push(getModuleInfo(deps[key]));
			}
		}
	});
	// console.log(allModuleInfo); // 所有文件路径，所需要的依赖，对应的文件代码
	/* 	[
		{
			file: "./src\\add.js",
			deps: {},
			code: "",
		},
	]; */

	allModuleInfo.forEach((moduleInfo) => {
		depsGraph[moduleInfo.file] = {
			deps: moduleInfo.deps,
			code: moduleInfo.code,
		};
	});
	// 使用对象存储，是为了后面文件中require的参数是一个地址，我们就可以通过这个地址从对象找出对应文件信息
	// 该模块的依赖（deps），该模块转化成es5的代码
	console.log(depsGraph);
	return depsGraph;
	// console.log(depsGraph);
};

/*
 * 生成一个bundle.js文件，也就是打包后的一个文件。其实思路很简单，就是把index.js的内容和它的依赖模块整合起来。然后把代码写到一个新建的js文件。
 * 处理两个关键字，require和export， 浏览器无法识别
 */
const bundle = (file) => {
	const depsGraph = JSON.stringify(parseModules(file));
	return `(function (graph) {
		 function require(file) {
			function absoluteRequire(realPath) {
				/* 例如传进来./src/index.js， 
				deps: { './add.js': './src\\add.js', './minus.js': './src\\minus.js' }
				这个文件中有require('./add.js') ==> graph[file].deps[realPath] = ./src/add.js
				*/
				return require(graph[file].deps[realPath]);
			}
			const exports = {};
			// require加载指定路径对应文件的代码, eval('xxxxx')
			(function (require, exports, code) {
				eval(code); // code中有require函数的调用，此时调用的就是传入进来的absoluteRequire
			})(absoluteRequire, exports, graph[file].code);
			return exports;
		}
		require('${file}');
	})(${depsGraph})`;
};

// getModuleInfo("./src/index.js");
// parseModules("./src/index.js");

const content = bundle("./src/index.js");

/* 创建文件，写入打包后的内容 */
fs.mkdirSync("./dist");
fs.writeFileSync("./dist/bundle.js", content);


